package bloodfang;

import java.util.Arrays;


public abstract class CycleRogue extends Cycle {
	
	protected double base_energy_regen;
	
	protected final double vanish_cd = 120.;
	protected final double vendetta_cd = 120.;
	protected final double killing_spree_cd = 120.;
	protected final double adrenaline_rush_cd = 180.;
	protected final double shadow_dance_cd = 60.;
	
	// WOD
	protected final double sr_cd = 120.;
	protected final double sr_duration = 8.;
	
	protected Attacks attacks;
	
	public double get_dps() {
		generate_aps();
//		aps.print();
		generate_dps();
		return dps.sum();
	}
	
	protected final double get_energy_regen() {
		double base_regen = getBaseEnergyRegen();
		double haste_perc = stats.getBuffed(Stat.hst) / RATING.HASTE_CONVERSION_RATING / 100.;
		double regen = base_regen * ( 1 + haste_perc );
		return regen;
	}

	protected abstract void generate_aps();
	
	protected final void generate_dps() {
		dps.clear();
		// the simplest and probably wrong implementation of shadow reflection
		double sr_uptime = sr_duration / sr_cd;
		for(String attack : aps.keySet()){
			if(attack.startsWith("rupture_applications")) continue;
			
			AttackData data = attacks.get_dmg(attack);
			double _dps = calc_dps_contribution(data);
			this.dps.add(data.getAttack(), _dps);
			if(Arrays.asList("eviscerate","rupture","sinister_strike","revealing_strike","ks_mh","ks_oh",
					"mutilate_mh","mutilate_oh","dispatch","envenom",
					"backstab","ambush","hemorrhage").contains(attack) && stats.hasTalent(Talent.shadow_reflection)) {
				this.dps.add("shadow_reflection", _dps * sr_uptime);
			}
			if(attack.equals("envenom") && stats.hasSetBonus(SetBonus.T17_4PC)){
				AttackData dp_dmg = attacks.get_dmg("deadly_poison").multiply(4.).multiply(0.35);
				double _dps_dp = calc_dps_contribution(dp_dmg);
				this.dps.add("deadly_poison_t17", _dps_dp);
			}
			
			// multistrike stat
			double chance = stats.getMultistrikeChance();
			double baseDamage = _dps * 0.3;
			double avgDamage = baseDamage;
			double doubleHit = 2 * chance * avgDamage;
			this.dps.add("multistrike", doubleHit);
			
			// cleave trinket
			if(!Arrays.asList("instant_poison").contains(attack)){	
				if(stats.getSettings().extra_targets > 0) {
					ProcInfo procInfo = null;
					for(String prefix : Procs.trinket_prefixes){
						if(stats.hasProc(prefix+"sigil_of_rampage"))
							procInfo = stats.getProcInfo(prefix+"sigil_of_rampage");
					}
					if(procInfo != null){
						ProcData procData = stats.getProcData().get(procInfo.getProcName());
						ProcBehavior procBehaviorCLEAVE = stats.getProcData().getBehavior("cleave");
						double scale = Cycle.get_scaling_value(procData, procInfo.getUpgradeLevel());
		        		double chancec = scale * procBehaviorCLEAVE.proc_chance / 100;
						this.dps.add("cleave", _dps * chancec * stats.getSettings().extra_targets);
					}
				}
			}
		}
	}
	
	protected final double calc_dps_contribution(AttackData ab){
		double hitDamage = ab.getHitDamage();
		double critDamage = ab.getCritDamage();
		
		double dmgMod = 1.0;
		if(ab.getType() == AttackType.PHYSICAL){
			if(stats.hasRaidBuff(RaidBuff.physicaldmg_debuff))
				dmgMod *= 1.05;
			dmgMod *= stats.getEffectiveDamage();
		}
		if(ab.getType() == AttackType.MAGICAL){
			if(stats.hasRaidBuff(RaidBuff.spelldmg_debuff))
				dmgMod *= 1.05;
		}
		dmgMod *= stats.getVersatilityMultiplier();
		dmgMod *= getSpecDamageMod(ab);
		
		hitDamage *= dmgMod;
		critDamage *= dmgMod;
		
		double aps = this.aps.get(ab.getAttack());
		double dps = 0.;
		if(ab.getAttack().equals("mh")){
			double swings = aps / stats.getMhHitChance();
			double hit = hitDamage * (stats.getMhHitChance() - ab.getCritChance());
			double crit = critDamage * ab.getCritChance();
			dps = (hit + crit) * swings;
			//dpsAllOnlyCrit = crit * swings;
		}else if(ab.getAttack().equals("oh")){
			double swings = aps / stats.getOhHitChance();
			double hit = hitDamage * (stats.getOhHitChance() - ab.getCritChance());
			double crit = critDamage * ab.getCritChance();
			dps = (hit + crit) * swings;
		}else{
			double hit = hitDamage * ( 1 - ab.getCritChance() );
			double crit = critDamage * ab.getCritChance();
			dps = (hit + crit) * aps;
		}
		return dps;
	}
	
	protected double calc_total_speed(double snd_speed){
		double speed = snd_speed * (1 + 0.01 * stats.getBuffed(Stat.hst) / RATING.HASTE_CONVERSION_RATING);
		if(stats.hasRaidBuff(RaidBuff.haste_buff))
			speed *= 1.1;
		if(stats.getRace() == Race.troll)
			speed *= 1 + .2 * 10. / 180;
		if(stats.getRace() == Race.golbin)
            speed *= 1.01;
		speed *= (1 + 0.3 * 40. / stats.getSettings().FIGHT_LENGTH);
		return speed;
	}
	
	protected double calc_total_speed() {
		double snd_speed = 1.4;
		return calc_total_speed(snd_speed);
	}
	
	protected abstract double getSpecDamageMod(AttackData ab);
	
	protected double getBaseEnergyRegen() {
		double venom_zest = stats.hasTalent(Talent.venom_zest) ? 0.05 : 0;
		return 10 * (1 + venom_zest);
	}
	
	protected final double pooling_time(double energy_regen, double min_energy, double max_energy){
		double min = energy_regen;
		double max = max_energy - (2 /*+ reaction_time*/) * energy_regen;
		double avg = (min + max) / 2 / energy_regen;
		return avg;
	}
	
	protected final double pooling_time(double energy_regen, double max_energy){
		double min = energy_regen;
		return pooling_time(energy_regen, min, max_energy);
	}
}
